界面布局 - Grid

前面介绍的 flexbox 布局是1维的, 本章介绍的 grid 网格布局 是2维的

基本概念

下面是一个 grid 布局 的示例 HTML


<head>
  <style>
  .wrapper {
    display: grid;  
    gap: 1.8em 1.3em;
    grid-template-columns: 100px 200px 300px;
  }
  .wrapper > div {
    border: 1px solid teal;
  }
  </style>
</head>

<body style="padding:1em">  

  <div class="wrapper">
    <div>1</div>
    <div>2</div>
    <div>3</div>
    <div>4</div>
    <div>5</div>
    <div>6</div>
    <div>7</div>
    <div>8</div>
    <div>9</div>
  </div>

</body>


看起来,有点像表格table

但是table是用来放表格数据的,而不是用来做界面布局的。


grid 是专门用来做界面布局,摆放 html元素的。


div.wrapperdisplay 值为 grid ,指定了

  • 内部显示规则是 grid 布局

  • 外部显示规则是 block

  • 该元素本身是一个 容器

    grid容器 (container) 是整个grid布局的最外层包含元素

    它的边界可以想象成 grid 的边框。

  • 该元素的直接子元素是一个个 网格子项

    grid item (子项) 是 container 的 直接子元素,

    它们 占据grid里面的 一个或者多个 网格(cell)

    一个网格通常具有许多的 列(column)行(row)

    行与行、列与列之间的间隙,一般被称为 沟槽(gutter)

    grid 的 纵线或者横线 之间的条状空间 被称之为 轨道(track)

列布局

grid 布局中 grid-template-columns 指定了 列 的数量 和 宽度

  <style>
  .wrapper {
    display: grid;  
    gap: 1.8em 1.3em;
    grid-template-columns: 100px 200px 300px;
  }
  .wrapper > div {
    border: 1px solid teal;
  }
  </style>

其中 gap: 1.8em 1.3emrow-gapcolumn-gap 的缩写

{
  row-gap: 1.8em;
  column-gap: 1.3em;
}

表明 行间距是 1.8em , 列间距是 1.3em


如果 row-gap 和 column-gap 一样,比如都是 1em,可以更简单的写为

{
  gap: 1em;
}



我们重点来看 grid-template-columns

{
  grid-template-columns: 200px 300px 400px;
}

它指定了 网格有3列,从左到右,每列的宽度分别是 200px、 300px、 400px

wrap里面的5个div 子项元素,各占一个网格

第4个子项 超过了总列数3,就占据了第2行第1列这个网格。


如果你想指定每列的宽度占container宽的比例,而不是指定网格的固定宽度

可以这样

{
 grid-template-columns: 1fr 2fr 3fr;
}


它指定了 网格有3列,从左到右,每列的宽度分别是 container宽的 1/6、 1/3 (2/6)、 1/2 (3/6)

指定比例的好处是:网格的列宽可以随着 container 宽度的变化而动态的变化,始终占满 container宽度


也可以使用百分比,表示这列占 container 的宽的 百分比

{
 grid-template-columns: 20% 30% 40%;
}




也可以 固定宽度 和 比例宽度 混合使用

{
 grid-template-columns: 200px 1fr 2fr;
}

它指定了 网格有3列,从左到右,第1列的宽度固定为 200px, 剩余2列的宽度分别是 container除了第1列剩余宽度的 1/3、 2/3



如果 有连续的宽度描述相同,可以使用 repeat() 标记

比如

{
  /* 等价于 200px 200px 200px */
  grid-template-columns: repeat(3, 200px);

 
  /* 等价于 1fr 1fr 1fr 1fr  */
  grid-template-columns: repeat(4, 1fr);

 
  /* 等价于 20px 1fr 1fr 1fr 1fr  */
  grid-template-columns: 20px repeat(4, 1fr);

 
  /* 等价于  1fr 2fr 1fr 2fr 1fr 2fr  */
  grid-template-columns: repeat(3, 1fr 2fr);

}



使用非固定宽度,比如 1fr 1fr 1fr ,如果cell里面的内容超过了当前cell的设定宽度,cell会自动变宽以适应内容的宽度。

如下


<head>
  <style>
  .wrapper {
    display: grid;    
    gap: .3em;
    grid-template-columns: 1fr 1fr 1fr;
  }
  .wrapper > div {
    border: 1px solid teal;
  }
  </style>
</head>

<body>  

<div class="wrapper">
  <div>1</div>
  <div>2222222222222222222222222222222222222</div>
  <div>3</div>
  <div>4</div>
  <div>5</div>
  <div>6</div>
  <div>7</div>
  <div>8</div>
  <div>9</div>
</div>

</body>


而如果 指定固定宽度,比如 200px 200px 200px , 如果cell里面的内容超过了当前cell的设定宽度,cell边界不会变化,cell里面的内容会跨越边界。


grid-template-columns 的值还可以是 auto , 这种情况列宽主要是由 列中条目item元素的大小 决定

比如


<head>
  <style>
  .wrapper {
    display: grid;    
    gap: .3em;
    grid-template-columns: 1fr auto auto;
  }
  .wrapper > div {
    border: 1px solid teal;
  }
  </style>
</head>

<body>  

<div class="wrapper">
  <div>1</div>
  <div>2</div>
  <div>3</div>
  <div>4</div>
</div>

</body>

可以发现 1fr auto auto 中 1fr 指定的第1列占据了几乎所有的宽度,

剩余 auto 指定的 第2、3列 只占据item元素的宽度,本例中就是1个字符的宽度。

但是,如果我们把 第2、3列 内容增多,比如改为

  <div>222222222222</div>
  <div>333333333333</div>

auto 指定的 第2、3列 占据的宽度就会相应的变大。



行布局

grid-template-rows 指定了 行 的数量 和 行高度

但是和 grid-template-columns 不同的是, 它并 不能限定 grid布局里面 总的行数

比如


<head>
  <style>
  .wrapper {
    display: grid;
    gap: .5em; 
    grid-template-rows: 100px 100px;
  }

  .wrapper > div {
    border: 1px solid teal;
  }
  </style>
</head>

<body>  

<div class="wrapper">  
  <div>1</div>
  <div>2</div>
  <div>3</div>
  <div>4</div>
  <div>5</div>
  <div>6<br>6<br>6</div>
  <div>7</div>
  <div>8</div>
  <div>9</div>
  <div>10</div>
</div>

</body>

由于没有 grid-template-columns 指定列数,缺省值为none,结果就是只有1列。

这里 grid-template-rows: 100px 100px; 只能指定前2行的高度是 100px,

当元素超过 2 个 时,后续元素产生在新行中。 总数不受 grid-template-rows 指定 行数限制。


指定 grid-template-rows 时, 可以和 grid-template-columns 一起指定

比如

  .wrapper {
    display: grid;
    gap: .5em; 
    grid-template-rows: 100px 100px;
    grid-template-columns: 50px 50px;
  }



grid-template-rows 还可以是其它的值

比如 : repeatfr百分比auto

这些值的含义和 grid-template-columns 类似, 不再赘述。


看这个例子


<head>
  <style>
  body{
    height: 100vh;
    margin: 0;
    display: grid;
    gap: 5px;
    grid-template-rows:  auto 1fr auto;
  }    
  div {
    border: 1px solid teal;
  }
  </style>
</head>
<body> 
  <div>导航栏</div>
  <div>
    正文
    <pre>
        2
        2
        2
        2
        2
        2
        2        
    </pre>
  </div>
  <div>页脚</div>
</body>


其中 grid-template-rows: auto 1fr auto 指定了3行内容,

导航栏 和 页脚 的值为 auto ,表示该行的高度 由其内容决定

而正文 值为 1fr ,并且没有其它fr,表示该行的高度占满除了 导航栏 和 页脚以外的全部高度

隐式指定track

前面我们这样用 grid-template-rows: 100px 100px ,是一种 显式指定

也就是明确地指定行 的数量 和对应的 track 的 高度和宽度 。

还有一种方式 叫 隐式指定(全局指定)

比如:可以使用 grid-auto-rows 隐式指定 track 的 高度

  .wrapper {
    display: grid;
    gap: .8em .3em;
    grid-template-columns: 1fr 1fr 1fr;
    grid-auto-rows: 100px;
  }

这样,不管产生的有多少行, 都是 grid-auto-rows 指定的高度 100px。


还可以这样写

{  
  /* row 的高度由其内容决定 */
  grid-auto-rows: auto;

  /* row 的高度是container的10% */
  grid-auto-rows: 10%;

}

也可以是一组值 的 循环

{
  grid-auto-rows: 50px 100px 150px;
  grid-auto-rows: 0.5fr 3fr 1fr;
}

有时会这样写

{
  grid-auto-rows: minmax(100px, auto);
}

表示 row 的高度由其内容决定,但至少是 100px

minmax 第1参数指定最小值, 第2个参数指定最大值。

规则是保证 min 一定满足的情况下,尽量取max值

元素跨行跨列

网格线

grid布局,网格是由 网格线 划分而成的

从左到右,列网格线编号依次从1开始,

从上到下,行网格线编号依次从1开始

如下图所示

image


通常我们的表格子项元素 是 依次一个个 分别放在一个个网格中的。

一个子项 放 一个 网格

但是也可以根据自己的需要,让某个子项占据多个网格位置


grid-column-start , grid-column-end , grid-row-start , grid-row-end

这4个css属性分别指定 子项元素 占据 网格的起始线、终止线,从而指定了占据多少个网格

看下面的示例


<head>
  <style>
  .wrapper {
    display: grid;
    gap: .8em .3em;
    grid-template-columns: repeat(3, 1fr);
    grid-auto-rows: 100px;
  }

  .box1 {
    grid-column-start: 1;
    grid-column-end: 4;
    grid-row-start: 1;
    grid-row-end: 3;
  }

  .box2 {
    grid-column-start: 1;
    grid-row-start: 3;
    grid-row-end: 5;
  }
  
  
  .wrapper > div {
    border: 1px solid teal;
  }

  </style>
</head>

<body>  

<div class="wrapper">
  <div class="box1">One</div>
  <div class="box2">Two</div>
  <div class="box3">Three</div>
  <div class="box4">Four</div>
  <div class="box5">Five</div>
</div>

</body>

div.box1 被指定列网格线从1到4, 行网格线从1到3, 所以 占据了3列2行的空间。


结束边界线如果不指定,缺省为起始位置+1, 也就是占一格。

比如 div.box2 被指定列网格线从1到2, 行网格线从3到5, 所以 占据了1列2行的空间。


剩余的没有指定的 grid 条目元素,在不重叠的前提下, 尽量按照从上到下,从左到右 的次序 摆放。

简便写法

上面的

 .box1 {
    grid-column-start: 1;
    grid-column-end: 4;
    grid-row-start: 1;
    grid-row-end: 3;
  }

  .box2 {
    grid-column-start: 1;
    grid-row-start: 3;
    grid-row-end: 5;
  }

简便写法是

  .box1 {
    grid-column: 1 / 4;
    grid-row: 1 / 3;
  }

  .box2 {
    grid-column: 1;
    grid-row: 3 / 5;
  }

也可以使用 span 指定跨几个格子。

比如,上面的写法等价于

  .box1 {
    grid-column: 1 / span 3;
    grid-row: 1 / 3;
  }

  .box2 {
    grid-column: 1;
    grid-row: 3 / span 2;
  }

也可以使用负数指定网格线,表示反方向计数的网格线

  .box1 {
    grid-column: 1 / -1;
    grid-row: 1 / 3;
  }